import java.io.*;
import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class KnownPlainDecipher {

    private String Vector;//The IV
    private String SampleText;
    private String SampleTextCipher;
    private String PlainTextCipher;
    private HashMap<Character,Character> keys;
    private Boolean[] lower;
    private Boolean[] upper;
    private Boolean[] lowerAssigned;
    private Boolean[] upperAssigned;
    private List<String> blocks;
    private String TextCipherName;
    
    public KnownPlainDecipher(String vectorPath, String SampleTextPath, String SampleTextCipherPath, String PlainTextPath)
    {
    	this.TextCipherName=PlainTextPath;
        this.Vector=readFromFile(vectorPath);
        this.SampleText=readFromFile(SampleTextPath);
        this.SampleTextCipher=readFromFile(SampleTextCipherPath);
        this.PlainTextCipher=readFromFile(PlainTextPath);
        this.blocks = new ArrayList<String>();
        this.keys = new HashMap<Character, Character>();
        this.lower = new Boolean[26];
        this.upper = new Boolean[26];
        this.lowerAssigned = new Boolean[26];
        this.upperAssigned = new Boolean[26];
        for (int i = 0; i <26 ; i++) {
            this.lower[i]=false;
            this.upper[i]=false;
            this.lowerAssigned[i]=false;
            this.upperAssigned[i]=false;
        }
        if (this.Vector.endsWith("\n")) {
            this.Vector = this.Vector.substring(0, this.Vector.length() - 2);
        }
        if (this.SampleText.endsWith("\n")) {
            this.SampleText = this.SampleText.substring(0, this.SampleText.length() - 2);
        }
        if (this.SampleTextCipher.endsWith("\n")) {
            this.SampleTextCipher = this.SampleTextCipher.substring(0, this.SampleTextCipher.length() - 2);
        }
        if (this.PlainTextCipher.endsWith("\n")) {
            this.PlainTextCipher = this.PlainTextCipher.substring(0, this.PlainTextCipher.length() - 2);
        }

    }

    public void runPlainAttack()
    {
        CreatePartialKey();
        FindRemainingKeys();
        CreateKeyFile ();
    }


    /*
    creates a partial key base on the readeble text and the encrypted
    using xor. if the char agter the xor is a letter, the assignment is added to the "keys" map
     */
    private void CreatePartialKey()
    {
        String tempSampleText=this.SampleText;
        for (int i=this.SampleText.length(); i<2128; i++)
        {
            tempSampleText+="\0";
        }

        for (int i=0; i<tempSampleText.length(); i++)
        {
            char cText = tempSampleText.charAt(i);
            char cVector = this.Vector.charAt(i);
            char cXor = (char)(cText^cVector);
            if (Character.isLetter(cXor)&&!keys.containsKey(cXor))
            {
                char CipherChar=this.SampleTextCipher.charAt(i);
                this.keys.put(cXor,CipherChar);
                if (cXor>='a'&&cXor<='z')
                {
                    this.lower[(int)(cXor)-97]=true;
                }//if
                else
                {
                    this.upper[(int)(cXor)-65]=true;
                }//else
                if (CipherChar>='a'&&CipherChar<='z')
                {
                    this.lowerAssigned[(int)(CipherChar)-97]=true;
                }//if
                else
                {
                    this.upperAssigned[(int)(CipherChar)-65]=true;
                }//else
            }
        }


    }


    /*
    reaches for the rest of the remaining keys
    searches for remaining keys that are "false" in the lower and upper arrays
    receives a string of all available assignments
    and finds the most suitable value
     */
    private void FindRemainingKeys()
    {
        char key=' ';
        String options="";
        for (int i = 0; i <26 ; i++) {
            if (!this.lower[i])
            {
                key=(char)(i+97);
                options=FindUnAssigned();
                FindValuePerKey(key,options);
            }
            if (!this.upper[i])
            {
                key=(char)(i+65);
                options=FindUnAssigned();
                FindValuePerKey(key,options);
            }
        }
    }


    /*
    given a key and a string of optional values
    the method searches for the most suitible value
    by rating the "not legal" chars.
    the value with the least illegal chars is the chosen value
    it then, adds the key,value pair to the keys map
    and updates the arrays for the keys and the assignments
     */
    private void FindValuePerKey (char key, String options)
    {
        int BestRate=PlainTextCipher.length();
        char value=' ';
        int tempRate=0;
        for (int i = 0; i < options.length(); i++)
        {
            char currentValue=options.charAt(i);
            tempRate=Rate(key,currentValue);
            if (tempRate<BestRate)
            {
                BestRate=tempRate;
                value=currentValue;
            }
        }
        keys.put(key, value);
        if (key>='a'&&key<='z')
        {
            this.lower[(int)(key)-97]=true;
        }//if
        else
        {
            this.upper[(int)(key)-65]=true;
        }//else
        if (value>='a'&&value<='z')
        {
            this.lowerAssigned[(int)(value)-97]=true;
        }//if
        else
        {
            this.upperAssigned[(int)(value)-65]=true;
        }//else
    }


    /*
    the method receives a key and a value
    count how many illegal chars it gets after replacing the key and a xor function
     */
    private int Rate(char key, char value)
    {
        //List<String> updatedBlocks=new ArrayList<String>();
        int rate=0;

            //for (int i = 0; i <this.blocks.size() ; i++)
        //int blocksSize=PlainTextCipher.length()/8128;
        int blockLength = 8128;
        StringBuilder tempUpdatedBlock;
        String PrevBlock = "";
        int t = PlainTextCipher.length();
        for (int i=0; i<PlainTextCipher.length(); i+=blockLength)
            {

                tempUpdatedBlock=new StringBuilder();
                //for (int j = 0; j < tempBloc.length(); j++)
                for (int j = 0; j < 8128; j++)
                {
                    char currentPlainChar = this.PlainTextCipher.charAt(i+j);
                    tempUpdatedBlock.append(currentPlainChar);
                    if (currentPlainChar == value)
                    {
                        //tempUpdatedBlock+=value;
                        char currentIVChar = ' ';

                        if (i == 0) {
                            currentIVChar = this.Vector.charAt(j);
                        } else {
                            currentIVChar = PrevBlock.charAt(j);
                        }


                        char currentXor = (char) (key ^ currentIVChar);


                        if ((currentXor > (char) 122 || currentXor < (char) 97) && currentXor != (char) 13 && currentXor != (char) 10 && currentXor != (char) 32) {
                            rate++;
                        }
                    }
                    else
                    {
                        //tempUpdatedBlock += currentPlainChar;
                    }
                }//if j
                PrevBlock=tempUpdatedBlock.toString();
            }//if i

        
        return rate;
    }


    /*
    returns a string of current unnassigned values
     */
    private String FindUnAssigned()
    {
        String UnAssigned="";
        char c=' ';
        for (int i = 0; i <26 ; i++) {
            if (!this.lowerAssigned[i])
            {
                c=(char)(97+i);
                UnAssigned+=c;
            }
            if (!this.upperAssigned[i])
            {
                c=(char)(65+i);
                UnAssigned+=c;
            }
        }


        return UnAssigned;
    }


    private String readFromFile(String file){
        String ans = "";
        try{ans = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);}
        catch(IOException e)
        {
            System.out.println("CAUSE: " + e.getCause() + "\nMESSAGE: " + e.getMessage());
            return e.getMessage();
        }
        return ans;
    }



    private void CreateKeyFile ()
    {
        //String name="cipherText_key.txt";
    	String name=this.TextCipherName;
    	name=name.substring(0,name.length()-4);
        name=name+"_key.txt";
        File file = new File(name);
        FileWriter fw;
        BufferedWriter bw = null;
        try {
            fw = new FileWriter(file.getAbsoluteFile());
            bw = new BufferedWriter(fw);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }


            try{
                StringBuilder key= new StringBuilder();
                for (int i = 97; i <123; i++){

                    key.append((char)i+ " "+this.keys.get((char)i).toString()+ "\n");

                }
                for (int i = 65; i <91; i++) {

                    key.append((char)i +" "+ this.keys.get((char)i).toString() + "\n");

                }

                    bw.write(key.toString());

            }
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }


        try {
            bw.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
